package com.jsbs.iam.ident.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jsbs.iam.common.core.dto.Result;
import com.jsbs.iam.common.core.enums.ResultEnum;
import com.jsbs.iam.common.core.utils.DateUtils;
import com.jsbs.iam.common.core.utils.DigestUtils;
import com.jsbs.iam.common.core.utils.Json;
import com.jsbs.iam.common.redis.service.RedisUtils;
import com.jsbs.iam.ident.auth.SessionInfo;
import com.jsbs.iam.ident.auth.TokenInfo;
import com.jsbs.iam.ident.constant.Constants;
import com.jsbs.iam.ident.constant.RedisConstants;
import com.jsbs.iam.ident.dto.IamTokenInfoDto;
import com.jsbs.iam.ident.dto.IdentAdminRoleUserDto;
import com.jsbs.iam.ident.dto.IdentAdminUserRoleVo;
import com.jsbs.iam.ident.dto.IdentApplyTypeCodeDto;
import com.jsbs.iam.ident.entity.IamApplySystem;
import com.jsbs.iam.ident.entity.IamMultideviceStrategy;
import com.jsbs.iam.ident.feign.IdentFeignInter;
import com.jsbs.iam.ident.mapper.IamMultideviceStrategyMapper;
import com.jsbs.iam.ident.service.SessionService;
import com.jsbs.iam.ident.utils.TokenHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName: SessionServiceImpl
 * @Author: junwc
 * @Date: 2022/12/26 11:18
 * @Description:
 */
@Slf4j
@Service
public class SessionServiceImpl implements SessionService {

    @Autowired
    private IamMultideviceStrategyMapper iamMultideviceStrategyMapper;

    @Autowired
    private IdentFeignInter identFeignInter;

    @Value("${_session.timeout:720}")
    private Long sessionTimeout;

    @Value("${_jwt.secret}")
    private String jwtSecret;

    @Value("${_session.max.count:1}")
    private Long sessionMaxCount;

    @Value("${_env.name}")
    private String envName;

    @Override
    public void createSession(HttpServletRequest request, HttpServletResponse response, SessionInfo sessionInfo,
                              List<IdentAdminUserRoleVo> adminUserRoleVoList,
                              String applyUrl, String userType) throws UnsupportedEncodingException {
        LocalDateTime expireTime = LocalDateTime.now().plusMinutes(sessionTimeout);
        sessionInfo.setExpireTime(expireTime);
        TokenInfo tokenInfo = TokenHelper.createToken(sessionInfo.getPhone(), sessionInfo.getUserCode(), sessionInfo.getIp(), jwtSecret);
        String token = tokenInfo.getToken();
        // userCode
        String redisKey = getRedisKey(sessionInfo.getUserCode(), sessionInfo.getIp(), token);

        String redisZSetKey = getRedisKey4ZSet(sessionInfo.getUserCode());

        Boolean MultiDeviceLogin = true;
        Boolean DownLineFirst = null;
        Boolean isStopSecondLogin = null;
        if (!Constants.USER_TYPE_ADMIN.equals(userType)) {
            IdentApplyTypeCodeDto identApplyTypeCodeDto = new IdentApplyTypeCodeDto();
            identApplyTypeCodeDto.setApplyUrl(applyUrl);
            Result result = identFeignInter.queryApplyTypeCode(identApplyTypeCodeDto);
            Object data = result.getData();
            String applyTypeCode = null;
            if (data != null) {
                applyTypeCode = data.toString();
            } else {
                throw new RuntimeException("根据Url查询到的应用类型为空，请检查url是否正确");
            }
            IamMultideviceStrategy multiDeviceStrategy = iamMultideviceStrategyMapper.getApplyTypeCode(applyTypeCode);
            if (multiDeviceStrategy != null) {
                //是否允许多设备登录
                MultiDeviceLogin = multiDeviceStrategy.getIsMultideviceLogin();
                //是否下线上一个设备
                DownLineFirst = multiDeviceStrategy.getIsDownlineFirst();
                //是否阻止下一个个设备登录
                isStopSecondLogin = multiDeviceStrategy.getIsStopSecondLogin();
            }

        }
        if (!MultiDeviceLogin) {
            //查询上次登录ip
            String ip = RedisUtils.get(envName + Constants.MULTI_DEVICE+sessionInfo.getUserCode());
            if (StringUtils.isNotEmpty(ip)) {
                //多设备登录取上次登录token
                String deviceToken = RedisUtils.get(envName + Constants.MULTI_DEVICE+ip);
                //本次登录ip对比上次登录ip
                if (sessionInfo.getIp().equals(ip)) {
                    if (DownLineFirst!= null && DownLineFirst) {
                        //删除上次登录token
                        RedisUtils.del(envName + getRedisKey(tokenInfo.getUserCode(), ip, deviceToken));
                    } else if (isStopSecondLogin != null && isStopSecondLogin) {
                        throw new RuntimeException("此用户已经在另一台设备上登录");
                    }
                }
            } else {
                RedisUtils.set(envName + Constants.MULTI_DEVICE+sessionInfo.getUserCode(),sessionInfo.getIp());
                RedisUtils.set(envName + Constants.MULTI_DEVICE+sessionInfo.getIp(),token);
            }

        }
        //计算zset的容量，如果超出阈值，就将最快要过期的session移除
        long count = RedisUtils.zcard(envName + redisZSetKey);
        long gap = count - sessionMaxCount + 1;
        if(gap > 0){
            Set<String> redisKeys = RedisUtils.zrange(envName + redisZSetKey, 0, gap - 1);
            RedisUtils.del(envName + redisKeys);
            RedisUtils.zremrangebyrank(envName + redisZSetKey, 0, gap - 1);
        }
        //失效时间作为score
        long score = DateUtils.asMilli(expireTime);
        // 按score值有小到大排序
        RedisUtils.zadd(envName + redisZSetKey, redisKey, score);
        RedisUtils.expire(envName + redisZSetKey, sessionTimeout + 1, TimeUnit.MINUTES);

        RedisUtils.setObject(envName + redisKey, sessionInfo, sessionTimeout, TimeUnit.MINUTES);

        //cookie存储 token  ip  user_code
        Cookie cookieToken = new Cookie(Constants.HEADER_TOKEN, token);
        cookieToken.setMaxAge(90 * 60);
//        cookieToken.setDomain(request.getHeader(Constants.X_FORWARDED_FOR));
        cookieToken.setDomain(request.getHeader(Constants.X_APP_TYPE));
        /*cookieToken.setDomain("test1-iam-pc-ae.yuanguhui.com");*/
        cookieToken.setPath("/");
        cookieToken.setHttpOnly(true);
        response.addCookie(cookieToken);
        Cookie cookieIp = new Cookie(Constants.IP, sessionInfo.getIp());
        cookieIp.setMaxAge(90 * 60);
        cookieIp.setDomain(request.getHeader(Constants.X_APP_TYPE));
        /*cookieIp.setDomain("test1-iam-pc-ae.yuanguhui.com");*/
        cookieIp.setPath("/");
        cookieIp.setHttpOnly(true);
        response.addCookie(cookieIp);
        Cookie cookieUserCode = new Cookie(Constants.USER_CODE, sessionInfo.getUserCode());
        cookieUserCode.setMaxAge(90 * 60);
        cookieUserCode.setDomain(request.getHeader(Constants.X_APP_TYPE));
        /*cookieUserCode.setDomain("test1-iam-pc-ae.yuanguhui.com");*/
        cookieUserCode.setPath("/");
        cookieUserCode.setHttpOnly(true);
        response.addCookie(cookieUserCode);
        if (sessionInfo.getUserCode().equals(Constants.ADMIN)) {
            response.setHeader(Constants.ADMIN_ROLE_CODE, Constants.ADMIN);
        } else if (CollectionUtils.isNotEmpty(adminUserRoleVoList)) {
            //获取第一个角色编码
            String adminRoleCode = adminUserRoleVoList.get(0).getAdminRoleCode();
            response.setHeader(Constants.ADMIN_ROLE_CODE, adminRoleCode);

            response.setHeader(Constants.ADMIN_ROLE_LIST, URLEncoder.encode(JSON.toJSONString(adminUserRoleVoList), "UTF-8"));
        }


        // 请求头设置用户信息
//        setHeaderInfo(response, token, sessionInfo);
    }

    /**
     * 功能描述：请求头设置用户信息
     * @param response
     * @param token
     * @param sessionInfo
     */
    private void setHeaderInfo(HttpServletResponse response, String token, SessionInfo sessionInfo) {
        response.setHeader(Constants.HEADER_TOKEN, token);
        response.setHeader(Constants.COMPANY_CODE, sessionInfo.getCompanyCode());
        response.setHeader(Constants.ORG_TYPE_CODE, sessionInfo.getOrgTypeCode());
        response.setHeader(Constants.NAME, sessionInfo.getName());
        response.setHeader(Constants.USER_CODE, sessionInfo.getUserCode());
//        response.setHeader(Constants.USER_ID, sessionInfo.getUserId());
        response.setHeader(Constants.PHONE, sessionInfo.getPhone());
        response.setHeader(Constants.EMAIL, sessionInfo.getEmail());
    }

    private String getRedisKey(String userCode, String ip, String token){
        return RedisConstants.REDIS_PREFIX + ":PC:" + userCode + ":" + ip + ":" + DigestUtils.md5Hex(token);
    }

    private String getRedisKey4ZSet(String userCode){
        return RedisConstants.REDIS_PREFIX + ":PC:SET:" + userCode;
    }

    @Override
    public Result validateSession(IamTokenInfoDto tokenInfoDto){
        // 根据token获取用户信息
        Result tokenInfoResult = getTokenInfo(tokenInfoDto.getToken());
        if(!tokenInfoResult.isSuccess()){
            log.info("Route=>SessionServiceImpl method=>validateSession 会话认证根据token解析用户信息失败token:{}", tokenInfoDto.getToken());
            return tokenInfoResult;
        }
        TokenInfo tokenInfo = (TokenInfo) tokenInfoResult.getData();
        //检查Redis
        String redisKey = getRedisKey(tokenInfo.getUserCode(), tokenInfo.getIp(), tokenInfo.getToken());
        SessionInfo sessionInfo = RedisUtils.getObject(envName + redisKey, SessionInfo.class);
        log.info("Route=>SessionServiceImpl method=>validateSession 会话认证请求根据key:{}获取用户信息info:{}", redisKey, Json.toJsonString(sessionInfo));
        if(null == sessionInfo){
            return Result.build(ResultEnum.SESSION_EXPIRE);
        }
        // 根据访问域名回去应用编码
        Result result = identFeignInter.getApplySystemByUrl(tokenInfoDto.getServerName());
        if (result.isSuccess()) {
            // 获取应用
            IamApplySystem applySystem = JSONObject.parseObject(Json.toJsonString(result.getData()),IamApplySystem.class);
            sessionInfo.setApplyCode(applySystem.getApplyCode());
        }
        touchSession(tokenInfo, sessionInfo, false);
        return Result.success(sessionInfo);
    }

    @Override
    public Result<SessionInfo> querySessionInfo(HttpServletRequest request,IamTokenInfoDto tokenInfoDto) {
        Result tokenInfoResult = getTokenInfo(tokenInfoDto.getToken());
        if(!tokenInfoResult.isSuccess()){
            return tokenInfoResult;
        }
        TokenInfo tokenInfo = (TokenInfo) tokenInfoResult.getData();
        //检查Redis
        String redisKey = getRedisKey(tokenInfo.getUserCode(), tokenInfo.getIp(), tokenInfo.getToken());
        SessionInfo sessionInfo = RedisUtils.getObject(envName + redisKey, SessionInfo.class);
        String userType = request.getHeader(Constants.X_USER_TYPE);
        if (Constants.USER_TYPE_ADMIN.equals(userType)) {
            IdentAdminRoleUserDto adminRoleUserDto = new IdentAdminRoleUserDto();
            adminRoleUserDto.setAccount(sessionInfo.getUserCode());
            List<IdentAdminUserRoleVo> adminUserRoleVoList =
                    identFeignInter.queryAdminRoleByUser(adminRoleUserDto).getData();
            if (CollectionUtils.isNotEmpty(adminUserRoleVoList)){
                String adminRoleCode = adminUserRoleVoList.get(0).getAdminRoleCode();
                if (sessionInfo.getUserCode().equals(Constants.ADMIN)) {
                    //超级管理员
                    sessionInfo.setAdminRoleCode(Constants.ADMIN);
                }
                sessionInfo.setList(adminUserRoleVoList);
            }
        }
        if(null == sessionInfo){
            return Result.build(ResultEnum.SESSION_EXPIRE);
        }
        return Result.success(sessionInfo);
    }

    private Result getTokenInfo(String token){
        TokenInfo tokenInfo = TokenHelper.validateToken(token, jwtSecret);
        if(null == tokenInfo){
            return Result.build(ResultEnum.SESSION_EXPIRE);
        }
        return Result.success(tokenInfo);
    }

    private void touchSession(TokenInfo tokenInfo, SessionInfo sessionInfo, boolean force){
        if(!force) {
            //如果不是强制延长时间，则判断是否需要续期
            LocalDateTime expireTime = sessionInfo.getExpireTime();
            long remainSeconds = Duration.between(LocalDateTime.now(), expireTime).getSeconds();
            //如果剩余时间小于过期时间的一半，则需要续期
            if (remainSeconds >= sessionTimeout * 30) {
                return;
            }
        }
        String redisZSetKey = getRedisKey4ZSet(tokenInfo.getUserCode());
        String redisKey = getRedisKey(tokenInfo.getUserCode(), tokenInfo.getIp(), tokenInfo.getToken());

        //失效时间作为score
        LocalDateTime newExpireTime = LocalDateTime.now().plusMinutes(sessionTimeout);
        sessionInfo.setExpireTime(newExpireTime);
        long score = DateUtils.asMilli(newExpireTime);
        RedisUtils.zadd(envName + redisZSetKey, redisKey, score);
        RedisUtils.expire(envName + redisZSetKey, sessionTimeout + 1, TimeUnit.MINUTES);
        RedisUtils.setObject(envName + redisKey, sessionInfo, sessionTimeout, TimeUnit.MINUTES);
    }
}
